//
// Created by Administrator on 2021/8/25.
//

#include "glad/glad.h"
#include "GLFW/glfw3.h"

#include "Common.h"
#include "Shader/ShaderProgram.h"
#include "Geometry/Matrix4x4.h"

GLuint VAO;
ShaderProgram SP;
Matrix4x4 mvMatrix;
Matrix4x4 projectionMatrix;

void FrameSizeChanged(GLFWwindow* window, int w, int h);
void Init();
void ProcessInput(GLFWwindow* window);
void Draw();

int main()
{
    if(!glfwInit())
    {
        ERROR("glfwInit failed!")
        glfwTerminate();
        return -1;
    }

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
#ifdef MAC_OS
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
#else
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
#endif
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef MAC_OS
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
#endif

    GLFWwindow* window = glfwCreateWindow(ScreenWidth, ScreenHeight, "Transform Geometry", nullptr, nullptr);
    if(!window)
    {
        ERROR("glfwCreateWindow failed!")
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);

    if(!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress))
    {
        ERROR("gladLoadGLLoader failed!")
        glfwDestroyWindow(window);
        glfwTerminate();
        return -1;
    }

    Init();
    FrameSizeChanged(window, ScreenWidth, ScreenHeight);
    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();
        ProcessInput(window);
        Draw();
        glfwSwapBuffers(window);
    }

    glDeleteVertexArrays(1, &VAO);
    glDeleteProgram(SP.id());

    glfwDestroyWindow(window);
    glfwTerminate();

    return 0;
}

void FrameSizeChanged(GLFWwindow* window, int w, int h)
{
    glViewport(0, 0, w, h);
    float aspect = float(w) / float(h);
    float near = 0.1f;
    float far = 1000.0f;
    float n = abs(near);
    float f = abs(far);
    float fov = 50.0f;
    float t = tanf(fov * 0.5f * DEG_2_RAD) * near;
    float b = -t;
    float r = t * aspect;
    float l = -r;
    projectionMatrix = Matrix4x4(2 * n / (r - l), 0, (l + r) / (l - r), 0,
                                 0, 2 * n / (t - b), (b + t) / (b - t), 0,
                                 0, 0, (n + f) / (n - f), 2 * f * n / (n - f),
                                 0, 0, -1, 0);
}

void Init()
{
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);

    ShaderInfo info("../../Chapter2/res/geo_vert.glsl", "../../Chapter2/res/geo_frag.glsl");
    SP = ShaderProgram(info);

    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);

    // cube vertex positions
    static const GLfloat vertexPositions[] = {
            // back
            -0.25f, 0.25f, -0.25f,
            0.25f, 0.25f, -0.25f,
            0.25f, -0.25f, -0.25f,

            0.25f, -0.25f, -0.25f,
            -0.25f, -0.25f, -0.25f,
            -0.25f, 0.25f, -0.25f,

            // forward
            -0.25f, 0.25f, 0.25f,
            -0.25f, -0.25f, 0.25f,
            0.25f, -0.25f, 0.25f,

            0.25f, -0.25f, 0.25f,
            0.25f, 0.25f, 0.25f,
            -0.25f, 0.25f, 0.25f,

            // left
            -0.25f, 0.25f, -0.25f,
            -0.25f, -0.25f, -0.25f,
            -0.25f, -0.25f, 0.25f,

            -0.25f, -0.25f, 0.25f,
            -0.25f, 0.25f, 0.25f,
            -0.25f, 0.25f, -0.25f,

            // right
            0.25f, 0.25f, 0.25f,
            0.25f, -0.25f, 0.25f,
            0.25f, -0.25f, -0.25f,

            0.25f, -0.25f, -0.25f,
            0.25f, 0.25f, -0.25f,
            0.25f, 0.25f, 0.25f,

            // top
            -0.25f, 0.25f, -0.25f,
            -0.25f, 0.25f, 0.25f,
            0.25f, 0.25f, 0.25f,

            0.25f, 0.25f, 0.25f,
            0.25f, 0.25f, -0.25f,
            -0.25f, 0.25f, -0.25f,

            // bottom
            -0.25f, -0.25f, -0.25f,
            0.25f, -0.25f, -0.25f,
            0.25f, -0.25f, 0.25f,

            0.25f, -0.25f, 0.25f,
            -0.25f, -0.25f, 0.25f,
            -0.25f, -0.25f, -0.25f,
    };

    GLuint VBO;
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof vertexPositions, vertexPositions, GL_STATIC_DRAW);
    glVertexAttribBinding(0, 0);
    glVertexAttribFormat(0, 3, GL_FLOAT, GL_FALSE, 0);
    glBindVertexBuffer(0, VBO, 0, 3 * sizeof(GLfloat));
    //glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(0);
}

void ProcessInput(GLFWwindow* window)
{

}

void Draw()
{
    static const GLfloat bgColor[4] = {0.2f, 0.2f, 0.2f, 1.0f};
    glClearBufferfv(GL_COLOR, 0, bgColor);
    glClearBufferfi(GL_DEPTH_STENCIL, 0, 1.0f, 0);



    // set uniforms
    SP.Use();
    float value[16];
    projectionMatrix.GetData(value);
    SP.SetMatrix4("_ProjectionMatrix", value);

    glBindVertexArray(VAO);

    for(auto i = 0; i < 24; ++i)
    {
        // construct mv matrix
        float curTime = float(i) + float(glfwGetTime()) * 0.3f;
        float f = curTime * PI * 0.1f;
        Matrix4x4 translation = Matrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
                                          0.0f, 1.0f, 0.0f, 0.0f,
                                          0.0f, 0.0f, 1.0f, -20.0f);
        Matrix4x4 translation2 = Matrix4x4(1.0f, 0.0f, 0.0f, sinf(2.1f * f) * 2.0f,
                                           0.0f, 1.0f, 0.0f, cosf(1.7f * f) * 2.0f,
                                           0.0f, 0.0f, 1.0f, sinf(1.3f * f) * cosf(1.5f * f) * 2.0f);
        float theta = curTime * 45.0f * DEG_2_RAD;
        Matrix4x4 rotate = Matrix4x4(cosf(theta), 0, sinf(theta),
                                     0, 1, 0,
                                     -sinf(theta), 0, cosf(theta));
        theta = curTime * 21.0f * DEG_2_RAD;
        Matrix4x4 rotate2 = Matrix4x4(1, 0, 0,
                                      0, cosf(theta), sinf(theta),
                                      0, -sinf(theta), cosf(theta));
        mvMatrix = translation * rotate * rotate2 * translation2;

        mvMatrix.GetData(value);
        SP.SetMatrix4("_MvMatrix", value);
        glDrawArrays(GL_TRIANGLES, 0, 36);
    }
}